import 'dart:convert';
import 'dart:io';

import '../pd_log.dart';

/// IO 平台的日志账本：记录事件溯源（NDJSON）与当前快照（JSON）。
///
/// 设计包含两类文件：
/// - `pd_log_ledger.jsonl`：按行写入事件（created/modified/deleted/rotated...）；
/// - `pd_log_summary.json`：维护所有日志文件的最新状态快照，便于查询与分页。
///
/// 路径布局：位于日志根目录下的隐藏子目录 `.pd_log_meta/`。
class _MetaPaths {
  final String ledgerPath;
  final String summaryPath;
  const _MetaPaths(this.ledgerPath, this.summaryPath);
}

/// 上传状态枚举。
enum UploadState { unknown, pending, success, failed }

/// 快照条目：记录某日志文件的最新状态。
class LogRecord {
  final String path;
  final int sizeBytes;
  final int modifiedMs;
  final bool exists;
  final int createdMs;
  final int? deletedMs;
  final UploadState uploadState;
  final int? uploadedMs;
  final int uploadAttempts;
  final String? lastError;
  final int? year;
  final int? month;
  final int? day;

  const LogRecord({
    required this.path,
    required this.sizeBytes,
    required this.modifiedMs,
    required this.exists,
    required this.createdMs,
    this.deletedMs,
    this.uploadState = UploadState.unknown,
    this.uploadedMs,
    this.uploadAttempts = 0,
    this.lastError,
    this.year,
    this.month,
    this.day,
  });

  /// 派生一个修改后的条目。
  LogRecord copyWith({
    int? sizeBytes,
    int? modifiedMs,
    bool? exists,
    int? createdMs,
    int? deletedMs,
    UploadState? uploadState,
    int? uploadedMs,
    int? uploadAttempts,
    String? lastError,
    int? year,
    int? month,
    int? day,
  }) {
    return LogRecord(
      path: path,
      sizeBytes: sizeBytes ?? this.sizeBytes,
      modifiedMs: modifiedMs ?? this.modifiedMs,
      exists: exists ?? this.exists,
      createdMs: createdMs ?? this.createdMs,
      deletedMs: deletedMs ?? this.deletedMs,
      uploadState: uploadState ?? this.uploadState,
      uploadedMs: uploadedMs ?? this.uploadedMs,
      uploadAttempts: uploadAttempts ?? this.uploadAttempts,
      lastError: lastError ?? this.lastError,
      year: year ?? this.year,
      month: month ?? this.month,
      day: day ?? this.day,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'path': path,
      'sizeBytes': sizeBytes,
      'modifiedMs': modifiedMs,
      'exists': exists,
      'createdMs': createdMs,
      'deletedMs': deletedMs,
      'uploadState': uploadState.name,
      'uploadedMs': uploadedMs,
      'uploadAttempts': uploadAttempts,
      'lastError': lastError,
      'year': year,
      'month': month,
      'day': day,
    };
  }

  /// 从 JSON 恢复快照条目。
  static LogRecord fromJson(Map<String, dynamic> json) {
    final stateStr =
        (json['uploadState'] as String?) ?? UploadState.unknown.name;
    final state = UploadState.values.firstWhere(
      (e) => e.name == stateStr,
      orElse: () => UploadState.unknown,
    );
    return LogRecord(
      path: (json['path'] as String?) ?? '',
      sizeBytes: (json['sizeBytes'] as int?) ?? 0,
      modifiedMs: (json['modifiedMs'] as int?) ?? 0,
      exists: (json['exists'] as bool?) ?? false,
      createdMs: (json['createdMs'] as int?) ?? 0,
      deletedMs: json['deletedMs'] as int?,
      uploadState: state,
      uploadedMs: json['uploadedMs'] as int?,
      uploadAttempts: (json['uploadAttempts'] as int?) ?? 0,
      lastError: json['lastError'] as String?,
      year: json['year'] as int?,
      month: json['month'] as int?,
      day: json['day'] as int?,
    );
  }
}

/// 日志变更事件（仅写入 NDJSON，不强制反序列化）。
class LogEvent {
  final String eventType; // created|modified|deleted|rotated|upload_*
  final String path;
  final int tsMs;
  final Map<String, dynamic> data;

  const LogEvent(this.eventType, this.path, this.tsMs, [this.data = const {}]);

  Map<String, dynamic> toJson() {
    return {
      'eventType': eventType,
      'path': path,
      'tsMs': tsMs,
      ...data,
    };
  }
}

/// 事件溯源 + 快照 的轻量管理器。
class LogLedger {
  static String? _testOverrideRoot;
  static const _metaDirName = '.pd_log_meta';
  static const _ledgerFileName = 'pd_log_ledger.jsonl';
  static const _summaryFileName = 'pd_log_summary.json';

  /// 测试用：设置根路径覆盖。
  /// - 在单元测试中将日志根目录重定向到临时路径；
  /// - 仅影响当前进程内的日志账本行为，不修改外部环境变量；
  /// - 传入 `null` 或空字符串可恢复默认解析逻辑。
  static void setTestOverrideRoot(String? path) {
    _testOverrideRoot = path;
  }

  static Future<String?> _resolveRoot() async {
    if (_testOverrideRoot != null && _testOverrideRoot!.isNotEmpty) {
      return _testOverrideRoot;
    }
    return PDLog.logRootPath();
  }

  static Future<_MetaPaths?> _paths() async {
    final root = await _resolveRoot();
    if (root == null || root.isEmpty) return null;
    final metaDir = Directory(_join(root, _metaDirName));
    if (!metaDir.existsSync()) {
      metaDir.createSync(recursive: true);
    }
    final ledger = _join(metaDir.path, _ledgerFileName);
    final summary = _join(metaDir.path, _summaryFileName);
    return _MetaPaths(ledger, summary);
  }

  /// 返回日志根目录的树状结构文本（用于调试/打印）。
  ///
  /// - [maxDepth] 控制遍历深度，避免过深目录影响性能；
  /// - 当根路径不可用或不存在时，返回可读的提示文本；
  /// - 不修改任何文件，仅读取目录结构。
  static Future<String> fileTreeAsText({int maxDepth = 4}) async {
    final root = await _resolveRoot();
    if (root == null || root.isEmpty) {
      return '日志根路径不可用（可能为 Web 平台或尚未初始化）。';
    }
    final dir = Directory(root);
    if (!await dir.exists()) {
      return '日志根路径不存在: $root';
    }
    final buf = StringBuffer();
    buf.writeln(root);
    await _walkDirToBuffer(dir, buf, prefix: '', depth: 0, maxDepth: maxDepth);
    return buf.toString();
  }

  static Future<void> _walkDirToBuffer(
    Directory dir,
    StringBuffer buf, {
    required String prefix,
    required int depth,
    required int maxDepth,
  }) async {
    try {
      final entities =
          await dir.list(recursive: false, followLinks: false).toList();
      entities.sort((a, b) => a.path.compareTo(b.path));
      for (var i = 0; i < entities.length; i++) {
        final e = entities[i];
        final isLast = i == entities.length - 1;
        final branch = isLast ? '└── ' : '├── ';
        final nextPrefix = prefix + (isLast ? '    ' : '│   ');
        final name = _basename(e.path);
        if (e is Directory) {
          buf.writeln('$prefix$branch$name/');
          if (depth < maxDepth) {
            await _walkDirToBuffer(e, buf,
                prefix: nextPrefix, depth: depth + 1, maxDepth: maxDepth);
          }
        } else if (e is File) {
          int size = 0;
          try {
            size = await e.length();
          } catch (_) {}
          buf.writeln('$prefix$branch$name ($size B)');
        } else {
          buf.writeln('$prefix$branch$name');
        }
      }
    } catch (err) {
      buf.writeln('$prefix[遍历失败: $err]');
    }
  }

  /// 读取 `pd_log_ledger.jsonl` 文件内容并返回字符串。
  static Future<String> readLedgerContent() async {
    final paths = await _paths();
    if (paths == null) {
      return '日志根路径不可用，无法读取 pd_log_ledger.jsonl。';
    }
    final f = File(paths.ledgerPath);
    if (!await f.exists()) {
      return '文件不存在: ${paths.ledgerPath}';
    }
    try {
      return await f.readAsString();
    } catch (e) {
      return '读取文件失败: ${paths.ledgerPath}, $e';
    }
  }

  /// 读取 `pd_log_summary.json` 文件内容并返回字符串。
  static Future<String> readSummaryContent() async {
    final paths = await _paths();
    if (paths == null) {
      return '日志根路径不可用，无法读取 pd_log_summary.json。';
    }
    final f = File(paths.summaryPath);
    if (!await f.exists()) {
      return '文件不存在: ${paths.summaryPath}';
    }
    try {
      return await f.readAsString();
    } catch (e) {
      return '读取文件失败: ${paths.summaryPath}, $e';
    }
  }

  /// 按给定绝对路径读取日志文件内容并返回字符串。
  /// 若文件不存在或读取失败，返回可读错误信息文本。
  static Future<String> readFileContent(String path) async {
    try {
      final f = File(path);
      if (!await f.exists()) {
        return '文件不存在: $path';
      }
      return await f.readAsString();
    } catch (e) {
      return '读取文件失败: $path, $e';
    }
  }

  static String _basename(String p) {
    final normalized = p.replaceAll('\\', '/');
    final idx = normalized.lastIndexOf('/');
    return idx >= 0 ? normalized.substring(idx + 1) : normalized;
  }

  static String _join(String a, String b) {
    return a.endsWith(Platform.pathSeparator)
        ? (a + b)
        : (a + Platform.pathSeparator + b);
  }

  /// 追加写入事件到 NDJSON 文件。
  static Future<void> _appendEvent(LogEvent evt) async {
    final p = await _paths();
    if (p == null) return;
    final file = File(p.ledgerPath);
    file.createSync(recursive: true);
    final line = jsonEncode(evt.toJson());
    await file.writeAsString('$line\n', mode: FileMode.writeOnlyAppend);
  }

  /// 读取快照 JSON；若文件不存在返回空 Map。
  static Map<String, dynamic> _readSummarySync(String summaryPath) {
    final f = File(summaryPath);
    if (!f.existsSync()) return <String, dynamic>{};
    try {
      final text = f.readAsStringSync();
      final obj = jsonDecode(text);
      if (obj is Map<String, dynamic>) return obj;
    } catch (_) {}
    return <String, dynamic>{};
  }

  static void _writeSummarySync(String summaryPath, Map<String, dynamic> data) {
    final tmp = File('$summaryPath.tmp');
    final f = File(summaryPath);
    tmp.createSync(recursive: true);
    tmp.writeAsStringSync(jsonEncode(data));
    // 原子重命名以降低并发写入风险
    if (f.existsSync()) {
      f.deleteSync();
    }
    tmp.renameSync(summaryPath);
  }

  /// 从文件系统扫描日志目录，并将最新状态同步至快照：
  /// - 对已存在条目进行字段更新（尺寸、修改时间、年月日）；
  /// - 对首次出现的文件写入 `created` 事件并新增快照条目；
  /// - 使用原子写入策略更新 `pd_log_summary.json`，降低并发风险。
  static Future<void> syncFromFiles(List<PDLogFile> files) async {
    final p = await _paths();
    if (p == null) return;
    final summaryPath = p.summaryPath;
    final data = _readSummarySync(summaryPath);
    for (final f in files) {
      final now = DateTime.now().millisecondsSinceEpoch;
      final existing = data[f.path];
      LogRecord rec;
      if (existing is Map<String, dynamic>) {
        rec = LogRecord.fromJson(existing).copyWith(
          sizeBytes: f.sizeBytes,
          modifiedMs: f.modifiedMs,
          exists: true,
          year: f.year,
          month: f.month,
          day: f.day,
        );
      } else {
        rec = LogRecord(
          path: f.path,
          sizeBytes: f.sizeBytes,
          modifiedMs: f.modifiedMs,
          exists: true,
          createdMs: now,
          year: f.year,
          month: f.month,
          day: f.day,
        );
        await _appendEvent(LogEvent('created', f.path, now, {
          'sizeBytes': f.sizeBytes,
          'modifiedMs': f.modifiedMs,
        }));
      }
      data[f.path] = rec.toJson();
    }
    _writeSummarySync(summaryPath, data);
  }

  /// 标记删除：写入事件并更新快照为不存在。
  static Future<void> recordDeletion(String path) async {
    final p = await _paths();
    if (p == null) return;
    final now = DateTime.now().millisecondsSinceEpoch;
    await _appendEvent(LogEvent('deleted', path, now, {
      'deletedMs': now,
      'exists': false,
    }));
    final data = _readSummarySync(p.summaryPath);
    final existing = data[path];
    if (existing is Map<String, dynamic>) {
      final rec = LogRecord.fromJson(existing).copyWith(
        exists: false,
        deletedMs: now,
      );
      data[path] = rec.toJson();
      _writeSummarySync(p.summaryPath, data);
    } else {
      final rec = LogRecord(
        path: path,
        sizeBytes: 0,
        modifiedMs: 0,
        exists: false,
        createdMs: now,
        deletedMs: now,
      );
      data[path] = rec.toJson();
      _writeSummarySync(p.summaryPath, data);
    }
  }

  static Future<void> markUploadStarted(String path) async {
    final p = await _paths();
    if (p == null) return;
    final now = DateTime.now().millisecondsSinceEpoch;
    await _appendEvent(LogEvent('upload_started', path, now));
    final data = _readSummarySync(p.summaryPath);
    final existing = data[path];
    LogRecord rec;
    if (existing is Map<String, dynamic>) {
      rec = LogRecord.fromJson(existing).copyWith(
        uploadState: UploadState.pending,
        uploadAttempts: (LogRecord.fromJson(existing).uploadAttempts + 1),
      );
    } else {
      rec = LogRecord(
        path: path,
        sizeBytes: 0,
        modifiedMs: 0,
        exists: false,
        createdMs: now,
        uploadState: UploadState.pending,
        uploadAttempts: 1,
      );
    }
    data[path] = rec.toJson();
    _writeSummarySync(p.summaryPath, data);
  }

  static Future<void> markUploadSuccess(String path) async {
    final p = await _paths();
    if (p == null) return;
    final now = DateTime.now().millisecondsSinceEpoch;
    await _appendEvent(LogEvent('upload_succeeded', path, now, {
      'uploadedMs': now,
    }));
    final data = _readSummarySync(p.summaryPath);
    final existing = data[path];
    if (existing is Map<String, dynamic>) {
      final rec = LogRecord.fromJson(existing).copyWith(
        uploadState: UploadState.success,
        uploadedMs: now,
      );
      data[path] = rec.toJson();
      _writeSummarySync(p.summaryPath, data);
    }
  }

  static Future<void> markUploadFailed(String path, String? error) async {
    final p = await _paths();
    if (p == null) return;
    final now = DateTime.now().millisecondsSinceEpoch;
    await _appendEvent(LogEvent('upload_failed', path, now, {
      'lastError': error ?? 'unknown',
    }));
    final data = _readSummarySync(p.summaryPath);
    final existing = data[path];
    if (existing is Map<String, dynamic>) {
      final rec = LogRecord.fromJson(existing).copyWith(
        uploadState: UploadState.failed,
        lastError: error ?? 'unknown',
      );
      data[path] = rec.toJson();
      _writeSummarySync(p.summaryPath, data);
    }
  }

  /// 读取与筛选快照列表：
  /// - 支持按上传状态过滤（`uploadState`）；
  /// - 支持按时间或路径排序，并设置方向（升/降）；
  /// - 支持按页大小与页码进行分页截断。
  static Future<List<LogRecord>> listSummary({
    ListOptions? options,
    UploadState? uploadState,
  }) async {
    final p = await _paths();
    if (p == null) return const [];
    final data = _readSummarySync(p.summaryPath);
    final list = <LogRecord>[];
    data.forEach((key, value) {
      if (value is Map<String, dynamic>) {
        list.add(LogRecord.fromJson(value));
      }
    });
    if (uploadState != null) {
      list.removeWhere((e) => e.uploadState != uploadState);
    }
    // 排序
    final opt = options ?? const ListOptions();
    list.sort((a, b) {
      int cmp;
      if (opt.by == SortBy.time) {
        // 使用 modifiedMs 优先，若为 0 则回退到 createdMs
        final at = a.modifiedMs > 0 ? a.modifiedMs : a.createdMs;
        final bt = b.modifiedMs > 0 ? b.modifiedMs : b.createdMs;
        cmp = at.compareTo(bt);
      } else {
        cmp = a.path.compareTo(b.path);
      }
      return opt.direction == SortDirection.asc ? cmp : -cmp;
    });
    // 分页
    if (opt.pageSize != null && (opt.pageSize ?? 0) > 0) {
      final ps = opt.pageSize!;
      final page = opt.page <= 0 ? 1 : opt.page;
      final start = (page - 1) * ps;
      if (start >= 0 && start < list.length) {
        final end = (start + ps) > list.length ? list.length : (start + ps);
        return list.sublist(start, end);
      } else {
        return const [];
      }
    }
    return list;
  }

  /// 记录“文件创建”事件：在平台文件系统上首次物理创建日志文件时调用。
  ///
  /// - 若快照已存在且标记为存在，则仅更新尺寸与修改时间，避免重复创建事件；
  /// - 若为新文件或曾被标记删除，则写入 `created` 事件并更新快照为存在；
  /// - 年/月/日信息将根据路径解析填充。
  static Future<void> recordCreated(
    String path, {
    int? sizeBytes,
    int? modifiedMs,
    int? tsMs,
  }) async {
    final p = await _paths();
    if (p == null) return;
    final summaryPath = p.summaryPath;
    final data = _readSummarySync(summaryPath);
    final now = tsMs ?? DateTime.now().millisecondsSinceEpoch;

    // 解析 yyyy/MM/dd 信息
    final pf = PDLogFile(
      path: path,
      sizeBytes: (sizeBytes ?? 0),
      modifiedMs: (modifiedMs ?? now),
    );

    final existing = data[path];
    if (existing is Map<String, dynamic>) {
      final rec0 = LogRecord.fromJson(existing);
      if (rec0.exists && (rec0.deletedMs == null)) {
        // 已存在的活跃文件：不重复写入创建事件，仅更新字段。
        final rec = rec0.copyWith(
          sizeBytes: sizeBytes ?? rec0.sizeBytes,
          modifiedMs: modifiedMs ?? rec0.modifiedMs,
          year: pf.year,
          month: pf.month,
          day: pf.day,
        );
        data[path] = rec.toJson();
        _writeSummarySync(summaryPath, data);
        return;
      } else {
        // 曾标记删除或不存在：视为重新创建。
        await _appendEvent(LogEvent('created', path, now, {
          'sizeBytes': sizeBytes ?? 0,
          'modifiedMs': modifiedMs ?? now,
        }));
        final rec = rec0.copyWith(
          exists: true,
          deletedMs: null,
          createdMs: now,
          sizeBytes: sizeBytes ?? rec0.sizeBytes,
          modifiedMs: modifiedMs ?? rec0.modifiedMs,
          year: pf.year,
          month: pf.month,
          day: pf.day,
        );
        data[path] = rec.toJson();
        _writeSummarySync(summaryPath, data);
        return;
      }
    }

    // 全新文件：写入事件并创建快照条目。
    await _appendEvent(LogEvent('created', path, now, {
      'sizeBytes': sizeBytes ?? 0,
      'modifiedMs': modifiedMs ?? now,
    }));
    final rec = LogRecord(
      path: path,
      sizeBytes: sizeBytes ?? 0,
      modifiedMs: modifiedMs ?? now,
      exists: true,
      createdMs: now,
      year: pf.year,
      month: pf.month,
      day: pf.day,
    );
    data[path] = rec.toJson();
    _writeSummarySync(summaryPath, data);
  }
}
